﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
using CredentialsManager.Common;
using CredentialsManager.Helpers;
using CredentialsManager.Properties;
using MVVMFoundation;

namespace CredentialsManager.ViewModels
{
    public class UserListViewModel : ViewModelBase
    {
        #region Private classes

        private class DeleteWorkerInput
        {
            public string Application { get; set; }
            public IList<string> UsersToDelete { get; set; }
        }

        #endregion

        #region Private member variables

        private string m_Filter;
        private ObservableCollection<string> m_AllUsers;
        private ReadOnlyCollection<string> m_Users;
        private ObservableCollection<string> m_SelectedUsers;
        private PasswordSettings m_PasswordSettings;
        private RelayCommand m_CreateUserCommand;
        private RelayCommand m_DeleteUserCommand;
        private RelayCommand m_ResetPasswordCommand;
        private RelayCommand m_UpdateUserCommand;
        private RelayCommand m_UpdatePasswordQuestionCommand;
        private RelayCommand m_ChangePasswordCommand;

        private BackgroundWorker m_DeleteWorker;

        #endregion

        #region Constructor

        public UserListViewModel(string application)
        {
            this.Application = application;
            this.SelectedUsers = new ObservableCollection<string>();
            this.ProgressMessage = string.Empty;

            var userProxy = ProxyFactory.Create<MembershipManagerClient, IMembershipManager>(ServiceAddressList.CredentialsService);
            var passwordProxy = ProxyFactory.Create<PasswordManagerClient, IPasswordManager>(ServiceAddressList.CredentialsService);

            try
            {
                m_AllUsers = userProxy.GetAllUsers(application);                
                m_PasswordSettings = passwordProxy.GetSettings(application);
                FilterUsers();
            }
            finally
            {
                ProxyFactory.Cleanup<MembershipManagerClient, IMembershipManager>(userProxy);
                ProxyFactory.Cleanup<PasswordManagerClient, IPasswordManager>(passwordProxy);
            }

            m_DeleteWorker = new BackgroundWorker();
            m_DeleteWorker.DoWork += m_DeleteWorker_DoWork;
            m_DeleteWorker.ProgressChanged += m_DeleteWorker_ProgressChanged;
            m_DeleteWorker.RunWorkerCompleted += m_DeleteWorker_RunWorkerCompleted;
        }

        #endregion

        #region Background worker handlers

        private void m_DeleteWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            var input = e.Argument as DeleteWorkerInput;
            Debug.Assert(input != null && input.UsersToDelete != null
                && input.UsersToDelete.Count > 0);

            var backgroundWorker = sender as BackgroundWorker;
            Debug.Assert(backgroundWorker != null);

            var proxy = ProxyFactory.Create<MembershipManagerClient, IMembershipManager>(ServiceAddressList.CredentialsService);

            try
            {
                DeleteUsersWithProgress(input, backgroundWorker, proxy);
                e.Result = proxy.GetAllUsers(input.Application);
            }
            finally
            {
                ProxyFactory.Cleanup<MembershipManagerClient, IMembershipManager>(proxy);
            }
        }

        private void m_DeleteWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.ProgressMessage = string.Format(Resources.DeletingUsersProgressMessage.ToString(),
                e.ProgressPercentage.ToString());
            Mediator.NotifyColleagues<string>(MediatorMessages.ProgressMessageChanged, this.ProgressMessage);
        }

        private void m_DeleteWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                ErrorHandler.HandleNonSpecificWCFException(e.Error, this.DisplayName);
            }
            else
            {
                m_AllUsers = e.Result as ObservableCollection<string>;
                Debug.Assert(m_AllUsers != null);
                FilterUsers();
            }

            this.ProgressMessage = string.Empty;
            this.IsLoading = false;
        }

        #endregion

        #region Public properties

        public string Application { get; private set; }

        public ReadOnlyCollection<string> Users
        {
            get
            {
                return m_Users;
            }
            private set
            {
                m_Users = value;
                RaisePropertyChanged(MethodBase.GetCurrentMethod().GetPropertyName());
            }
        }

        public ObservableCollection<string> SelectedUsers
        {
            get
            {
                return m_SelectedUsers;
            }
            set
            {
                m_SelectedUsers = value;

                m_SelectedUsers.CollectionChanged += delegate(
                    object sender, NotifyCollectionChangedEventArgs e)
                {
                    Mediator.NotifyColleagues<object>(MediatorMessages.SelectedUserChanged, null);
                };                


                RaisePropertyChanged(MethodBase.GetCurrentMethod().GetPropertyName());                
            }
        }

        public string Filter
        {
            get
            {
                return m_Filter;
            }
            set
            {
                m_Filter = value;
                RaisePropertyChanged(MethodBase.GetCurrentMethod().GetPropertyName());
                FilterUsers();
            }
        }

        public bool EnabledPasswordReset
        {
            get
            {
                return m_PasswordSettings.EnabledPasswordReset;
            }
        }

        public bool RequiresQuestionAndAnswer
        {
            get
            {
                return m_PasswordSettings.RequiresQuestionAndAnswer;
            }
        }

        #endregion

        #region Private methods

        private void DeleteUsersWithProgress(DeleteWorkerInput input,
            BackgroundWorker backgroundWorker, MembershipManagerClient proxy)
        {
            int lastProgress = 0;

            for (int index = 0; index < input.UsersToDelete.Count; index++)
            {
                proxy.DeleteUser(input.Application, input.UsersToDelete[index], true);
                int progress = (index / input.UsersToDelete.Count) * 100;

                if (progress > lastProgress)
                {
                    lastProgress = progress;
                    backgroundWorker.ReportProgress(progress);
                }
            }
        }

        private void CreateUser()
        {
            var createUserDialog = base.GetService<IDialogService>();
            Debug.Assert(createUserDialog != null);

            var userViewModel = new CreateUserViewModel(this.Application, m_PasswordSettings);
            createUserDialog.ShowModal(userViewModel);            
            
            var newUsers = from nu in userViewModel.NewUsers
                           join u in m_AllUsers
                           on nu equals u into j
                           from u in j.DefaultIfEmpty()
                           where u == null
                           select nu;

            foreach (var newUser in newUsers)
            {
                m_AllUsers.Add(newUser);
            }

            FilterUsers();
        }

        private void FilterUsers()
        {
            if (string.IsNullOrEmpty(this.Filter) || this.Filter.Trim() == string.Empty)
            {
                this.Users = new ReadOnlyCollection<string>(m_AllUsers);
            }
            else
            {
                var users = (from u in m_AllUsers
                             where u.ToUpper().Contains(this.Filter.ToUpper())
                             select u).ToList<string>();

                this.Users = new ReadOnlyCollection<string>(users);
            }
        }

        private void DeleteUser()
        {
            Debug.Assert(!this.IsLoading && !m_DeleteWorker.IsBusy);

            if (m_SelectedUsers != null && m_SelectedUsers.Count > 0)
            {
                IMessageBoxService messageBox = base.GetService<IMessageBoxService>();
                Debug.Assert(messageBox != null);

                string message = Resources.ConfirmUsersDeletionMessage;

                if (m_SelectedUsers.Count == 1)
                {
                    message = Resources.ConfirmUserDeletionMessage;
                }

                MessageBoxResult result = messageBox.Show(message, this.DisplayName,
                    MessageBoxButton.YesNo, MessageBoxImage.Question);

                if (result == MessageBoxResult.Yes)
                {
                    this.IsLoading = true;
                    var workerInput = new DeleteWorkerInput()
                    {
                        Application = this.Application, 
                        UsersToDelete = this.SelectedUsers
                    };

                    m_DeleteWorker.RunWorkerAsync(workerInput);
                }
            }
        }

        private void ResetPassword()
        {
            if (this.SelectedUsers.Count != 1)
            {
                throw new InvalidOperationException(
                    Resources.SingleUserNotSelectedInvalidOperationMessage);
            }

            if (!m_PasswordSettings.EnabledPasswordReset)
            {
                throw new InvalidOperationException(
                    Resources.ResetPasswordNotEnabledErrorMessage);
            }

            var resetPasswordViewModel = new ResetPasswordViewModel(m_PasswordSettings,
                this.Application, this.SelectedUsers[0]);

            if (!m_PasswordSettings.RequiresQuestionAndAnswer)
            {
                resetPasswordViewModel.ResetPassword();
            }

            var resetDialog = base.GetService<IDialogService>();
            Debug.Assert(resetDialog != null);
            resetDialog.ShowModal(resetPasswordViewModel);
        }

        private void UpdateUser()
        {
            if (this.SelectedUsers.Count != 1)
            {
                throw new InvalidOperationException(
                    Resources.SingleUserNotSelectedInvalidOperationMessage);
            }

            var updateUserViewModel = new UpdateUserViewModel(this.Application,
                this.SelectedUsers[0], m_PasswordSettings);

            var updateUserDialog = base.GetService<IDialogService>();
            Debug.Assert(updateUserDialog != null);
            updateUserDialog.ShowModal(updateUserViewModel);
        }

        private void UpdatePasswordQuestion()
        {
            if (this.SelectedUsers.Count != 1)
            {
                throw new InvalidOperationException(
                    Resources.SingleUserNotSelectedInvalidOperationMessage);
            }

            if (!m_PasswordSettings.RequiresQuestionAndAnswer)
            {
                throw new InvalidOperationException(
                    Resources.CannotUpdatePasswordQuestionInvalidOperationExceptionMessage);
            }

            var updatePasswordQuestionViewModel = new UpdatePasswordQuestionViewModel(
                this.Application, this.SelectedUsers[0], m_PasswordSettings);

            var updatePasswordQuestionDialog = base.GetService<IDialogService>();
            Debug.Assert(updatePasswordQuestionDialog != null);
            updatePasswordQuestionDialog.ShowModal(updatePasswordQuestionViewModel);
        }

        private void ChangePassword()
        {
            if (this.SelectedUsers.Count != 1)
            {
                throw new InvalidOperationException(
                    Resources.SingleUserNotSelectedInvalidOperationMessage);
            }

            var changePasswordViewModel = new ChangePasswordViewModel(this.SelectedUsers[0],
                this.Application, m_PasswordSettings);
            var changePasswordDialog = base.GetService<IDialogService>();
            Debug.Assert(changePasswordDialog != null);
            changePasswordDialog.ShowModal(changePasswordViewModel);
        }

        #endregion

        #region Commands

        public ICommand CreateUserCommand
        {
            get
            {
                return m_CreateUserCommand ?? (m_CreateUserCommand = new RelayCommand(
                    param => this.CreateUser()));
            }
        }

        public ICommand DeleteUserCommand
        {
            get
            {
                return m_DeleteUserCommand ?? (m_DeleteUserCommand = new RelayCommand(
                    param => this.DeleteUser()));
            }
        }

        public ICommand ResetPasswordCommand
        {
            get
            {
                return m_ResetPasswordCommand ?? (m_ResetPasswordCommand = new RelayCommand(
                    param => this.ResetPassword()));
            }
        }

        public ICommand UpdateUserCommand
        {
            get
            {
                return m_UpdateUserCommand ?? (m_UpdateUserCommand = new RelayCommand(
                    param => this.UpdateUser()));
            }
        }

        public ICommand UpdatePasswordQuestionCommand
        {
            get
            {
                return m_UpdatePasswordQuestionCommand ?? (m_UpdatePasswordQuestionCommand = new RelayCommand(
                    param => this.UpdatePasswordQuestion()));
            }
        }

        public ICommand ChangePasswordCommand
        {
            get
            {
                return m_ChangePasswordCommand ?? (m_ChangePasswordCommand = new RelayCommand(
                    param => this.ChangePassword()));
            }
        }

        #endregion
    }
}
